#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/epoll.h>
#include <infiniband/verbs.h>
#include <rdma/rdma_cma.h>

#include "iser.h"
#include "etcd.h"

/* usually rx_task is passed directly from the rx handler,
 * but due to the connection establishment event-data race
 * the first login task may be saved and injected afterwards.
 */
void iser_login_rx(void *arg)
{
        int ret = 0;
        char *ip = NULL;
        struct iser_task *rx_task = arg;
        struct iser_conn *conn = rx_task->conn;
        struct iser_task *tx_task = conn->login_tx_task;
        struct iscsi_login_rsp_hdr *rsp_bhs =
                (struct iscsi_login_rsp_hdr *)tx_task->pdu.bhs;

        if (conn->h.state == STATE_START) {
                DERROR("conn:%p, not established yet, delaying login_rx\n",
                        conn);
                conn->h.state = STATE_READY;
                conn->login_rx_task = rx_task;
                conn->h.refcount --;
                YASSERT(conn->h.refcount >= 0);
                return;
        }

        iser_login_exec(&conn->h, &rx_task->pdu, &tx_task->pdu);
        if (rsp_bhs->status_class) {
                DERROR("conn:%p, login failed, class:%0x detail:%0x\n",
                        conn, rsp_bhs->status_class, rsp_bhs->status_detail);
        }

        if (conn->login_phase == LOGIN_PHASE_LAST_SEND) {
                DINFO("transitioning to full-feature, no repost\n");
        } else
                iser_dev_post_recv(conn, rx_task, 1);

        schedule_resp_tx(tx_task, conn);

        ip = inet_ntoa(((struct sockaddr_in *)&conn->peer_addr)->sin_addr);
        ret = etcd_create_text("client_iplist", ip, ip, 0);
        if (unlikely(ret)) {
                if (ret != EEXIST)
                        DWARN("add initiator %s to etcd failed, ret: %d !!!!\n", ip, ret);
        }

        conn->h.refcount --;
        YASSERT(conn->h.refcount >= 0);
}

int iser_nop_out_rx(struct iser_task *task)
{
        int ret = 0;
        struct iser_conn *conn = task->conn;
        struct iscsi_nop_out_hdr *req_bhs =
                (struct iscsi_nop_out_hdr *) task->pdu.bhs;

        if (req_bhs->ttt != cpu_to_be32(ISCSI_RESERVED_TAG)) {
                /*
                 * When sending NOP-In, we don't request a NOP-Out                                     .
                 * by sending a valid Target Tranfer Tag.
                 * See iser_send_ping_nop_in() and 10.18.2 in the draft 20.
                 */
                DERROR("conn:%p task:%p initiator bug, ttt not reserved\n",
                        conn, task);
                ret = -ISCSI_REASON_PROTOCOL_ERROR;
                goto reject;
        }
        if (req_bhs->itt == cpu_to_be32(ISCSI_RESERVED_TAG)) {
                if (req_bhs->opcode & ISCSI_OP_IMMEDIATE) {
                        DINFO("No response to Nop-Out\n");
                        iser_dev_post_recv(conn, task, 1);
                        return -EAGAIN; /* ToDo: fix the ret codes */
                } else {
                        DERROR("conn:%p task:%p initiator bug, "
                                "itt reserved with no imm. flag\n", conn, task);
                        ret = -ISCSI_REASON_PROTOCOL_ERROR;
                        goto reject;
                }
        }

        task->out_len = ntoh24(req_bhs->dlength);
        //DINFO("conn:%p nop-out task:%p cmdsn:0x%x data_sz:%d\n",
        //        conn, task, task->cmd_sn, task->out_len);

        return 0;

reject: /* ToDo: prepare and send reject pdu */
        /* iser_send_reject(task, ISCSI_REASON_INVALID_PDU_FIELD); */
        return ret;
}

void iser_login_tx_complete(struct iser_conn *conn)
{
        switch (conn->h.state) {
        case STATE_SECURITY_LOGIN:
                conn->h.state = STATE_LOGIN;
                break;
        case STATE_SECURITY_FULL: /* fall through */
        case STATE_LOGIN_FULL:
                DINFO("conn:%p, last login send completed\n", conn);
                conn->h.state = STATE_FULL;
                iser_conn_login_phase_set(conn, LOGIN_PHASE_FF);
                iser_conn_free_login_resources(conn);
                break;
        }
}

int iser_tm_exec(struct iser_task *task)
{
        struct iser_conn *conn = task->conn;
        //struct iscsi_session *session = conn->h.session;
        struct iscsi_task_mgt_hdr *req_bhs = (struct iscsi_task_mgt_hdr *) task->pdu.bhs;
        int fn = 0;
        int ret = 0;

        DERROR("conn:%p TM itt:0x%x cmdsn:0x%x "
                "ref.itt:0x%x ref.cmdsn:0x%x "
                "exp.cmdsn:0x%x "
                "lun:0x%04x%04x%04x%04x\n",
                conn,
                be32_to_cpu(req_bhs->itt), be32_to_cpu(req_bhs->cmd_sn),
                be32_to_cpu(req_bhs->rtt), be32_to_cpu(req_bhs->ref_cmd_sn),
                be32_to_cpu(req_bhs->exp_stat_sn),
                req_bhs->lun[0], req_bhs->lun[1], req_bhs->lun[2], req_bhs->lun[3]);

        switch (req_bhs->function & ISCSI_FUNCTION_MASK) {
        case ISCSI_FUNCTION_ABORT_TASK:
                fn = ABORT_TASK;
                break;
        case ISCSI_FUNCTION_ABORT_TASK_SET:
                fn = ABORT_TASK_SET;
                break;
        case ISCSI_FUNCTION_CLEAR_ACA:
                fn = CLEAR_TASK_SET;
                break;
        case ISCSI_FUNCTION_CLEAR_TASK_SET:
                fn = CLEAR_ACA;
                break;
        case ISCSI_FUNCTION_LOGICAL_UNIT_RESET:
                fn = LOGICAL_UNIT_RESET;
                break;
        case ISCSI_FUNCTION_TARGET_WARM_RESET:
        case ISCSI_FUNCTION_TARGET_COLD_RESET:
        case ISCSI_FUNCTION_TASK_REASSIGN:
                ret = ISCSI_RESPONSE_FUNCTION_UNSUPPORTED;
                DERROR("unsupported TMF %d\n",
                        req_bhs->function & ISCSI_FUNCTION_MASK);
                break;
        default:
                ret = ISCSI_RESPONSE_FUNCTION_REJECTED;
                DERROR("unknown TMF %d\n",
                        req_bhs->function & ISCSI_FUNCTION_MASK);
        }

        if (ret)
                task->result = ret;
        else {
                int ret = 0;
                UNIMPLEMENTED(__WARN__);
                (void) fn;
                /*
                ret = target_mgmt_request(session->target->tid, session->sid.id.tsih,
                                          (unsigned long) task, fn, req_bhs->lun,
                                          req_bhs->rtt, 0);
                */
                UNIMPLEMENTED(__WARN__);

                set_task_in_scsi(task);
                iser_conn_get(conn);

                switch (ret) {
                case MGMT_REQ_QUEUED:
                        break;
                case MGMT_REQ_FAILED:
                case MGMT_REQ_DONE:
                        clear_task_in_scsi(task);
                        iser_conn_put(conn);
                        break;
                }
        }
        return ret;
}

int iser_data_out_rx(struct iser_task *dout_task)
{
        int ret = 0;
        struct iser_conn *conn = dout_task->conn;
        struct iscsi_session *session = conn->h.session;
        struct iscsi_data_out_hdr *req_bhs =
            (struct iscsi_data_out_hdr *) dout_task->pdu.bhs;
        struct iser_task *task;

        list_for_each_entry(task, &session->cmd_list, session_list) {
                if (task->tag == req_bhs->itt)
                        goto found;
        }
        return -EINVAL;

found:
        iser_task_add_out_pdu_buf(task, &dout_task->pdu.membuf,
                                  be32_to_cpu(req_bhs->buffer_offset));
        list_add_tail(&dout_task->dout_task_list, &task->dout_task_list);

        /* ToDo: BUG!!! add an indication that it's data-out task so that
 *                 it can be released when the buffer is released */

        task->unsol_remains -= ntoh24(req_bhs->datalength);

        DINFO("task:%p tag:0x%04"PRIx64 ", dout taskptr:%p out_len:%d "
                "uns_rem:%d rdma_rd_rem:%d expcmdsn:0x%x\n",
                task, task->tag, dout_task, task->out_len,
                task->unsol_remains, task->rdma_rd_remains,
                session->exp_cmd_sn);

        /* ToDo: look at the task counters !!! */
        if (req_bhs->ttt == cpu_to_be32(ISCSI_RESERVED_TAG)) {
                if (req_bhs->flags & ISCSI_CMD_FINAL) {
                        if (!task_pending(task)) {
                                /* ToDo: this condition is weird ... */
                                if (task->rdma_rd_remains == 0 && task->unsol_remains == 0)
                                        schedule_task_iosubmit(task, conn);
                        }
                }
        } else {
                if (!(req_bhs->flags & ISCSI_CMD_FINAL))
                        return ret;

                if (task->rdma_rd_remains == 0 && task->unsol_remains == 0)
                        schedule_task_iosubmit(task, conn);
        }
        return ret;
}

int iser_logout_exec(struct iser_task *task)
{
        struct iser_conn *conn = task->conn;
        struct iscsi_logout_rsp_hdr *rsp_bhs =
                (struct iscsi_logout_rsp_hdr *) task->pdu.bhs;

        memset(rsp_bhs, 0, BHS_SIZE);
        rsp_bhs->opcode = ISCSI_OP_LOGOUT_RSP;
        rsp_bhs->flags = ISCSI_CMD_FINAL;
        rsp_bhs->response = ISCSI_LOGOUT_SESSION;
        rsp_bhs->itt = task->tag;
        rsp_bhs->stat_sn = cpu_to_be32(conn->h.stat_sn++);
        rsp_bhs->exp_cmd_sn = cpu_to_be32(conn->h.exp_cmd_sn);
        rsp_bhs->max_cmd_sn = cpu_to_be32(conn->h.max_cmd_sn);

        task->pdu.ahssize = 0;
        task->pdu.membuf.size = 0;

        DINFO("rsp task:%p op:%0x itt:%0x statsn:%0x\n",
                task, (unsigned int)rsp_bhs->opcode,
                (unsigned int)rsp_bhs->itt,
                (unsigned int)rsp_bhs->stat_sn);

        /* add the logout resp to tx list, and force all scheduled tx now */
        list_add_tail(&task->tx_list, &conn->resp_tx_list);
        DINFO("add to tx list, logout resp task:%p tag:0x%04"PRIx64 " cmdsn:0x%x\n",
                task, task->tag, task->cmd_sn);
        iser_sched_remove_event(&conn->sched_tx);
        iser_sched_tx(&conn->sched_tx);

        return 0;
}

static inline void iser_set_rsp_stat_sn(struct iscsi_session *session,
                                        struct iscsi_hdr *rsp)
{
        if (session) {
                rsp->exp_sn = cpu_to_be32(session->exp_cmd_sn);
                rsp->max_sn = cpu_to_be32(session->exp_cmd_sn +
                                              ISER_MAX_QUEUE_CMD);
        }
}

int iser_nop_out_exec(struct iser_task *task)
{
        struct iscsi_nop_in_hdr *rsp_bhs = (struct iscsi_nop_in_hdr *) task->pdu.bhs;
        struct iser_conn *conn = task->conn;

        rsp_bhs->opcode = ISCSI_OP_NOP_IN;
        rsp_bhs->flags = ISCSI_CMD_FINAL;
        rsp_bhs->rsvd2 = 0;
        rsp_bhs->rsvd3 = 0;
        memset(rsp_bhs->lun, 0, sizeof(rsp_bhs->lun));
        rsp_bhs->itt = task->tag;
        rsp_bhs->ttt = ISCSI_RESERVED_TAG;
        rsp_bhs->statsn = cpu_to_be32(conn->h.stat_sn);
        if (task->tag != ISCSI_RESERVED_TAG)
                conn->h.stat_sn++;

        iser_set_rsp_stat_sn(conn->h.session, task->pdu.bhs);

        memset(rsp_bhs->rsvd4, 0, sizeof(rsp_bhs->rsvd4));
        task->pdu.ahssize = 0;
        task->pdu.membuf.size = task->out_len; /* ping back nop-out data */

        schedule_resp_tx(task, conn);
        return 0;
}

static int iser_task_handle_ahs(struct iser_task *task)
{
        struct iscsi_conn *conn = &task->conn->h;
        struct iscsi_scsi_cmd_hdr *req_bhs = (struct iscsi_scsi_cmd_hdr *) task->pdu.bhs;
        int ahslen = task->pdu.ahssize;
        void *ahs = task->pdu.ahs;
        struct iscsi_cmd *scmd = &task->scmd;

        if (ahslen >= 4) {
                struct iscsi_cdb_ahdr *ahs_extcdb = ahs;

                if (ahs_extcdb->ahstype == ISCSI_AHSTYPE_CDB) {
                        int extcdb_len = ntohs(ahs_extcdb->ahslength) - 1;
                        int total_cdb_len = sizeof(req_bhs->scb) + extcdb_len;
                        unsigned char *p;

                        if (4 + extcdb_len > ahslen) {
                                DERROR("AHS len:%d too short for extcdb %d\n",
                                        ahslen, extcdb_len);
                                return -EINVAL;
                        }
                        if (total_cdb_len > 260) {
                                DERROR("invalid extcdb len:%d\n", extcdb_len);

                                return -EINVAL;
                        }
                        p = malloc(total_cdb_len);
                        if (!p) {
                                DERROR("failed to allocate extdata len:%d\n", total_cdb_len);
                                return -ENOMEM;
                        }
                        memcpy(p, req_bhs->scb, sizeof(req_bhs->scb));
                        //p += sizeof(req_bhs->scb); // its crazy.
                        memcpy(p + sizeof(req_bhs->scb), ahs_extcdb->cdb, extcdb_len);
                        task->extdata = p;

                        scmd->pdu.ahs = ahs;
                        scmd->pdu.ahssize = total_cdb_len;

                        ahs += 4 + extcdb_len;
                        ahslen -= 4 + extcdb_len;
                }
        }

        if (task->is_write && task->is_read && ahslen >= 8) {
                struct iscsi_rlength_ahdr *ahs_bidi = ahs;
                if (ahs_bidi->ahstype == ISCSI_AHSTYPE_RLENGTH) {
                        uint32_t in_length = ntohl(ahs_bidi->read_length);

                        if (in_length)
                                task->in_len = roundup(in_length,
                                                       conn->tp->data_padding);
                        DINFO("bidi read len:%u, padded:%u\n",
                                in_length, task->in_len);
                }
        }

        return 0;
}

int iser_scsi_cmd_rx(struct iser_task *task)
{
        int ret = 0;
        struct iser_conn *conn = task->conn;
        struct iscsi_session *session = conn->h.session;
        struct iscsi_scsi_cmd_hdr *req_bhs = (struct iscsi_scsi_cmd_hdr *) task->pdu.bhs;
        unsigned int flags = req_bhs->flags;
        uint32_t imm_data_sz = ntoh24(req_bhs->datalength);
        uint32_t xfer_sz = ntohl(req_bhs->data_length);

        task->is_read = flags & ISCSI_CMD_READ;
        task->is_write = flags & ISCSI_CMD_WRITE;

        if (task->is_write) {
                task->out_len = xfer_sz;
                if (!task->is_read) {
                        /* reset irrelevant fields */
                        task->in_len = 0;
                        task->rdma_wr_sz = 0;
                        task->rdma_wr_remains = 0;
                }
        } else {
                if (likely(task->is_read)) {
                        task->in_len = xfer_sz;

                        /* reset irrelevant fields */
                        task->out_len = 0;
                        task->unsol_sz = 0;
                        task->unsol_remains = 0;
                        task->rdma_rd_sz = 0;
                        task->rdma_rd_remains = 0;

                } else {
                        task->out_len = 0;
                        task->in_len = 0;
                }
        }

        if (task->pdu.ahssize) {
                ret = iser_task_handle_ahs(task); /* may set task->in_len */
                if (ret)
                        goto out;
        }

        if (task->is_write) {
                /* add immediate data to the task */
                if (!conn->h.session_param[key_initial_r2t].val) {
                        task->unsol_sz = conn->h.session_param[key_first_burst_length].val;
                        if (task->out_len < task->unsol_sz)
                                task->unsol_sz = task->out_len;
                } else
                        task->unsol_sz = 0;

                if (conn->h.session_param[key_immediate_data].val) {
                        if (imm_data_sz > 0) {
                                if (task->unsol_sz == 0)
                                        task->unsol_sz = imm_data_sz;
                                iser_task_add_out_pdu_buf(task, &task->pdu.membuf, 0);
                        }
                } else if (unlikely(imm_data_sz > 0)) {
                        DERROR("ImmediateData disabled but received\n");
                        ret = -EINVAL;
                        goto out;
                }

                /* immediate data is the first chunk of the unsolicited data */
                task->unsol_remains = task->unsol_sz - imm_data_sz;
                /* rdma-reads cover the entire solicited data */
                task->rdma_rd_sz = task->out_len - task->unsol_sz;
                task->rdma_rd_remains = task->rdma_rd_sz;

                /* ToDo: multiple RDMA-Write buffers */
                /* task->rdma_rd_offset = task->unsol_sz; */
        }

        if (task->is_read) {
                task->rdma_wr_sz = task->in_len;
                task->rdma_wr_remains = task->in_len;
        }

        list_add_tail(&task->session_list, &session->cmd_list);
out:
        DBUG("task:%p tag:0x%04"PRIx64 " scsi_op:0x%x lun:%d %s%s in_len:%d out_len:%d "
                "imm_sz:%d unsol_sz:%d cmdsn:0x%x expcmdsn:0x%x\n",
                task, task->tag, req_bhs->scb[0],
                translate_lun(req_bhs->lun),
                task->is_read ? "rd" : "", task->is_write ? "wr" : "",
                task->in_len, task->out_len, imm_data_sz, task->unsol_sz,
                task->cmd_sn, session->exp_cmd_sn);

        return ret;
}

void iser_scsi_cmd_iosubmit(struct iser_task *task, int not_last)
{
        struct iscsi_scsi_cmd_hdr *req_bhs = (struct iscsi_scsi_cmd_hdr *) task->pdu.bhs;
        struct iscsi_cmd *scmd = &task->scmd;
        struct iser_conn *conn = task->conn;
        struct iscsi_session *session = conn->h.session;
        struct iser_membuf *data_buf;
        loff_t offset;
        u32 length;

        (void) not_last;

        scmd->callback = iser_scsi_cmd_done;
        scmd->sense_len = 0;
        memcpy(&scmd->pdu.bhs, req_bhs, sizeof(*req_bhs));
        scmd->conn = &conn->h;
        scmd->status = 0;

        scmd->lun = volume_get(session->target, translate_lun(req_bhs->lun));
        if (unlikely(!scmd->lun)) {
                switch (req_bhs->scb[0]) {
                        case INQUIRY:
                        case REPORT_LUNS:
                                break;
                        default:
                                DWARN("SCSI 0x%x: %s/%u not found\n",
                                                req_bhs->scb[0],
                                                session->target->name,
                                                translate_lun(req_bhs->lun));

                                //record_invalid_access(scmd);

                                /* If the initiator want to access a lun which has been
                                 *                         * removed, set sense key.
                                 *                                                 */
                                iscsi_cmd_set_sense(scmd, ILLEGAL_REQUEST, 0x25, 0x00);
                                conn->h.state = STATE_CLOSE;

                                goto out;
                }
        } else {
                DBUG("the lun id is %u in find lun path and cmd is 0x%x\n",
                                translate_lun(req_bhs->lun), req_bhs->scb[0]);
                /* Used for put volume when release this command */
                scmd->flags |= CMD_FLG_LUNIT;
        }

        switch (req_bhs->scb[0]) {
                case READ_6:
                case READ_10:
                case READ_16: {
                        /* ToDo: multiple RDMA-Read buffers */
                        data_buf = list_entry(task->in_buf_list.next,
                                        struct iser_membuf, task_list);

                        set_offset_and_length(scmd->lun, req_bhs->scb, &offset, &length);
                        scmd->tio = tio_alloc(&conn->h, 0);
                        /*
                         * Set the length of data want to read, and the device's offset
                         * where to read.
                         
			DWARN("iser begin read\n"); */
			YASSERT(data_buf->buf.len == 0);
			YASSERT(data_buf->buf.list.next == &data_buf->buf.list);
			YASSERT(scmd->tio->buffer.list.next == &scmd->tio->buffer.list);
                        tio_set_diskseek(scmd->tio, offset, length);

                        //scsi_set_in_buffer(scmd, data_buf->addr);
                        //scsi_set_in_length(scmd, task->in_len);
                        break;
                }
                case WRITE_6:
                case WRITE_10:
                case WRITE_16:
                case WRITE_VERIFY: {
                        /* It's either the last buffer, which is RDMA,
                         *                 or the only buffer */
                        data_buf = list_entry(task->out_buf_list.prev,
                                        struct iser_membuf, task_list);
                        /* ToDo: multiple RDMA-Write buffers */
                        if (unlikely(task->out_buf_num > 1)) {
                                unsigned char *ptr = data_buf->addr;
                                struct iser_membuf *cur_buf;
				DWARN("iser write buffer num is %d\n", task->out_buf_num);
                                list_for_each_entry(cur_buf, &task->out_buf_list, task_list) {
                                        if (cur_buf->rdma)
                                                break;
                                        memcpy(ptr, cur_buf->addr, cur_buf->size);
                                        ptr += cur_buf->size;
                                }
                                iser_task_free_dout_tasks(task);
                        }

                        set_offset_and_length(scmd->lun, req_bhs->scb, &offset, &length);
                        if (unlikely(iscsi_cmd_write_size(scmd) != length)) {
                                DERROR("itt: %x, %u, %u\n", cmd_itt(scmd), iscsi_cmd_write_size(scmd), length);
                        }

                        scmd->tio = tio_alloc(&conn->h, 0);

                        /*
                         * Set the length of data want to write, and the device's offset
                         * where to write.
                         */
			YASSERT(length <= (512 * 1024));
                        tio_set_diskseek(scmd->tio, offset, length);
                        
                        /*DWARN("before buffer init mem handler ptr %p\n", ((mem_handler_t *)data_buf->mem_handler)->ptr);
                        if (((mem_handler_t *)data_buf->mem_handler)->ptr  != data_buf->addr)
                                YASSERT(0);*/
                        //mbuffer_copy(&scmd->tio->buffer, data_buf->addr, task->out_len);
                        mbuffer_merge(&scmd->tio->buffer, &data_buf->buf);
			YASSERT(scmd->tio->io_len == scmd->tio->buffer.len);
                        //scsi_set_out_buffer(scmd, data_buf->addr);
                        //scsi_set_out_length(scmd, task->out_len);
                        break;
                }
            /*    default:
                         DWARN("found a meta command %x\n", req_bhs->scb[0]);*/
        }

        DBUG("task:%p tag:0x%04"PRIx64 "\n", task, task->tag);

        set_task_in_scsi(task);
        iser_conn_get(conn);

out:
        target_cmd_queue(scmd);
        return;
}

int iser_scsi_cmd_done(struct iscsi_cmd *scmd)
{
        struct iser_task *task = container_of(scmd, struct iser_task, scmd);
        struct iscsi_scsi_rsp_hdr *rsp_bhs =
                (struct iscsi_scsi_rsp_hdr *) task->pdu.bhs;
        struct iser_conn *conn = task->conn;
        struct iscsi_session *session = conn->h.session;
        unsigned char sense_len = scmd->sense_len;
        struct iser_membuf *data_buf;
        u32 size;

        struct iscsi_scsi_cmd_hdr *req_bhs = (struct iscsi_scsi_cmd_hdr *) task->pdu.bhs;
        
        YASSERT(memcmp(&scmd->pdu.bhs, req_bhs, sizeof(*req_bhs)) == 0);

        if (unlikely(conn->h.state != STATE_FULL)) {
                /* Connection is closed, but its resources are not released
                 * to allow receiving completion of such late tasks.
                 * When all tasks are released, and connection refcnt
                 * drops to zero, then all the resources can be freed. */
                iser_task_complete(task);
                return 0;
        }

        rsp_bhs->opcode = ISCSI_OP_SCSI_RSP;
        rsp_bhs->flags = ISCSI_FLG_FINAL;
        rsp_bhs->response = ISCSI_RESPONSE_COMMAND_COMPLETED;
        rsp_bhs->cmd_status = scmd->status;
        rsp_bhs->revd1[0] = 0;
        rsp_bhs->revd1[1] = 0;
        rsp_bhs->itt = task->tag;
        rsp_bhs->snack = 0;
        rsp_bhs->stat_sn = cpu_to_be32(conn->h.stat_sn++);
        iser_set_rsp_stat_sn(session, task->pdu.bhs);
        rsp_bhs->exp_data_sn = 0;

        //iscsi_rsp_set_residual(rsp_bhs, scmd);

        if (!sense_len && task->is_read) {
                data_buf = list_entry(task->in_buf_list.next,
                                struct iser_membuf, task_list);

                task->rdma_wr_remains = scmd->tio->buffer.len;
                task->rdma_wr_sz = scmd->tio->buffer.len;
		
		YASSERT(data_buf->buf.len == 0);
                mbuffer_merge(&data_buf->buf, &scmd->tio->buffer);
        }

        task->pdu.ahssize = 0;
        task->pdu.membuf.size = 0;

        if (unlikely(sense_len > 0)) {
                DWARN("sense len is %d\n", sense_len);
                struct iscsi_sense_data *sense =
                        (struct iscsi_sense_data *) task->pdu.membuf.addr;

                size = iscsi_cmd_write_size(scmd);
                if (size) {
                        if (cmd_scsi_hdr(scmd)->flags & ISCSI_CMD_WRITE) {
                                rsp_bhs->flags |= ISCSI_FLG_BIRESIDUAL_UNDERFLOW;
                                rsp_bhs->bi_residual_count = cpu_to_be32(size);
                        } else {
                                rsp_bhs->flags |= ISCSI_FLG_RESIDUAL_UNDERFLOW;
                                rsp_bhs->residual_count = cpu_to_be32(size);
                        }
                }

                sense->length = cpu_to_be16(sense_len);

                /* ToDo: need to fix the ISCSI_PARAM_INITIATOR_RDSL bug in initiator */
                YASSERT(sense_len + 2 <= (int)conn->rsize);

                memcpy(sense->data, scmd->sense_buf, sense_len);
                task->pdu.membuf.size = sense_len + sizeof(*sense);
        }

        if (likely(scmd->tio))
                tio_put(&conn->h, scmd);

        DBUG("task:%p tag:0x%04"PRIx64 " status:%x statsn:%d flags:0x%x "
                        "in_len:%d out_len:%d rsd:%d birsd:%d\n",
                        task, task->tag, rsp_bhs->cmd_status,
                        conn->h.stat_sn-1, rsp_bhs->flags,
                        //scsi_get_in_length(scmd), scsi_get_out_length(scmd),
                        0, 0,
                        ntohl(rsp_bhs->residual_count),
                        ntohl(rsp_bhs->bi_residual_count));

        schedule_resp_tx(task, conn);

        return 0;
}

